package resources;

import com.crunch.core.Renderer;
import com.crunch.graphics.gpu.GpuException;
import com.crunch.graphics.gpu.GpuProgram;
import com.crunch.graphics.gpu.GpuState;
import com.crunch.graphics.gpu.GpuTexture;
import util.ShaderLoader;

import javax.imageio.ImageIO;
import javax.media.opengl.GL;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.nio.IntBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Manages resources.
 */
public class ResourceManager {
	public ResourceManager(Renderer renderer) {
		this.renderer = renderer;
		textures = new HashMap<String, GpuTexture>();
		programs = new HashMap<String, GpuProgram>();
	}

	public void loadTexture(String name, URL url)
			throws IOException {
		if (textures.containsKey(name)) {
			throw new IllegalArgumentException("Resource " + name + " already specified");
		}
		final String nameF = name;
		final BufferedImage image = ImageIO.read(url);
		textures.put(name, null);

		// load as texture later
		renderer.queueCommand(new Renderer.Command() {
			@Override
			public void execute(GpuState gpuState) {
				GpuTexture.Texture2DDescription desc = new GpuTexture.Texture2DDescription();
				desc.imageDescription.internalFormat = GL.GL_SRGB_ALPHA;
				desc.imageDescription.minFilter = GL.GL_NEAREST;
				desc.imageDescription.magFilter = GL.GL_NEAREST;
				GpuTexture.Data2DDescription mipLevel = new GpuTexture.Data2DDescription();
				desc.mipmapLevels = new GpuTexture.Data2DDescription[] { mipLevel };
				mipLevel.width = image.getWidth();
				mipLevel.height = image.getHeight();
				mipLevel.format = GL.GL_RGBA;
				mipLevel.type = GL.GL_UNSIGNED_BYTE;
				int[] rgbaData = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
				// swap r and b bytes
				for (int i = 0; i < rgbaData.length; ++i) {
					rgbaData[i] =
							(rgbaData[i] & 0xFF00FF00) | (Integer.rotateLeft(rgbaData[i], 16) & 0x00FF00FF);
				}
				// flip y
				for (int y = 0; y < mipLevel.height/2; ++y) {
					for (int x = 0; x < mipLevel.width; ++x) {
						int i = y*mipLevel.width + x;
						int i1 = (mipLevel.height - y - 1)*mipLevel.width + x;
						int temp = rgbaData[i];
						rgbaData[i] = rgbaData[i1];
						rgbaData[i1] = temp;
					}
				}
				mipLevel.data = IntBuffer.wrap(rgbaData);
				desc.wrapS = GL.GL_REPEAT;
				desc.wrapT = GL.GL_REPEAT;
				GpuTexture texture = gpuState.createTexture(desc);
				textures.put(nameF, texture);
			}
		});
	}

	public GpuTexture getTexture(String name) {
		return textures.get(name);
	}

	public Map<String, GpuTexture> getTextures() {
		return Collections.unmodifiableMap(textures);
	}

	public void loadProgram(String name, URL vsUrl, URL gsUrl, URL fsUrl)
			throws IOException {
		if (programs.containsKey(name)) {
			throw new IllegalArgumentException("Resource " + name + " already specified");
		}
		final String nameF = name;
		final ShaderLoader loader = new ShaderLoader(vsUrl, gsUrl, fsUrl);
		programs.put(name, null);

		// load as texture later
		renderer.queueCommand(new Renderer.Command() {
			@Override
			public void execute(GpuState gpuState) {
				GpuProgram.ProgramDescription desc = new GpuProgram.ProgramDescription();
				desc.vertexShaderSource = (loader.getVertexShaderSource() == null) ? null :
						new String[] { loader.getVertexShaderSource() };
				desc.geometryShaderSource = (loader.getGeometryShaderSource() == null) ? null :
						new String[] { loader.getGeometryShaderSource() };
				desc.fragmentShaderSource = (loader.getFragmentShaderSource() == null) ? null :
						new String[] { loader.getFragmentShaderSource() };
				try {
					GpuProgram program = gpuState.createProgram(desc);
					programs.put(nameF, program);
				} catch (GpuException e) {
					System.out.println("Program " + nameF + " failed to load");
				}
			}
		});
	}

	public GpuProgram getProgram(String name) {
		return programs.get(name);
	}

	public Map<String, GpuProgram> getPrograms() {
		return Collections.unmodifiableMap(programs);
	}

	public void freeResources() {
		renderer.queueCommand(new Renderer.Command() {
			@Override
			public void execute(GpuState gpuState) {
				for (GpuTexture t : textures.values()) {
					t.release();
				}
				textures.clear();

				for (GpuProgram p : programs.values()) {
					p.release();
				}
				programs.clear();
			}
		});
	}

	private Renderer renderer;

	private HashMap<String, GpuTexture> textures;
	private HashMap<String, GpuProgram> programs;
}